package com.cy.pj.sys.service;

import com.cy.pj.common.exception.ServiceException;
import com.cy.pj.sys.dao.SysMenuDao;
import com.cy.pj.sys.dao.SysRoleMenuDao;
import com.cy.pj.sys.dao.SysUserDao;
import com.cy.pj.sys.dao.SysUserRoleDao;
import com.cy.pj.sys.pojo.SysUser;
import org.apache.catalina.User;
import org.apache.shiro.authc.*;
import org.apache.shiro.authc.credential.CredentialsMatcher;
import org.apache.shiro.authc.credential.HashedCredentialsMatcher;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.authz.SimpleAuthorizationInfo;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;
import org.apache.shiro.util.ByteSource;
import org.apache.shiro.util.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.HashSet;
import java.util.List;
import java.util.Set;

/**
 * @Author Yu
 * @create 2025/06/11
 * @description
 */
@Service
public class SysUserRealm extends AuthorizingRealm {

    @Autowired
    private SysUserDao sysUserDao;
    @Autowired
    private SysUserRoleDao sysUserRoleDao;
    @Autowired
    private SysRoleMenuDao sysRoleMenuDao;
    @Autowired
    private SysMenuDao sysMenuDao;

    /**
     HashedCredentialsMatcher 正是 CredentialsMatcher 的一个实现类写项目的话，
     总归会用到用户密码的非对称加密，目前主流的非对称加密方式是 MD5 ，
     以及在 MD5 上的加盐处理，而
     HashedCredentialsMatcher 也允许我们指定自己的算法和盐
     */
    @Override
    public CredentialsMatcher getCredentialsMatcher() {
        HashedCredentialsMatcher hashedCredentialsMatcher = new HashedCredentialsMatcher();//此处可以断点分析
        hashedCredentialsMatcher.setHashAlgorithmName("MD5");
        hashedCredentialsMatcher.setHashIterations(1);
        return hashedCredentialsMatcher;
    }



    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(
            PrincipalCollection principalCollection) {
        /*获取登录用户的信息*/
        // SysUser user1 = (SysUser) principalCollection;
        //1.获取登录用户信息，例如用户id
        SysUser user=(SysUser)principalCollection.getPrimaryPrincipal();
        Integer userId = user.getId();
        List<Integer> roleIds = sysUserRoleDao.findRoleIdsByUserId(userId);
        if (roleIds == null || roleIds.size() == 0){
            throw new ServiceException("当前用户没有角色");
        }
        /*  集合转数组 toArray方法 传入一个指定类型数组可以返回指定类型集合*/
        Integer[] arr = {};
        List<Integer> menuIds = sysRoleMenuDao.findMenuIdsByRoleIds(roleIds.toArray(arr));
        if (menuIds == null || menuIds.size() == 0){
            throw new ServiceException("当前角色没有权限");
        }
        List<String> permissions = sysMenuDao.findPermissions(menuIds.toArray(arr));
        /*使用Set集合存放可以去重*/
        Set<String> set = new HashSet();
        for (String per : permissions) {
            if (StringUtils.hasLength(per)){
                set.add(per);
            }
        }
        SimpleAuthorizationInfo sa =  new SimpleAuthorizationInfo();
        //将获取到的所有权限 封装到对象中
        sa.setStringPermissions(set);
        return sa;//返回给授权管理器
    }


    /**
     * 通过此方法完成认证数据的获取及封装,系统
     * 底层会将认证数据传递认证管理器，由认证
     * 管理器完成认证操作。
     */
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(
            AuthenticationToken authenticationToken) throws AuthenticationException {
        /*获取封装数据的UsernamePasswordToken 并根据username查询数据库*/
        UsernamePasswordToken upToken = (UsernamePasswordToken) authenticationToken;
        String username = upToken.getUsername();
        SysUser user = sysUserDao.findUserByUsername(username);
        /*校验用户信息*/
        if (user == null){
            throw new ServiceException("当前用户名错误");
        }
        if (user.getValid() == 0){
            throw new ServiceException("当前用户已经禁用");
        }
        /*根据Shiro框架要求将盐值转换类型*/
        ByteSource byteSourceSalt = ByteSource.Util.bytes(user.getSalt());
        /*封装登录认证数据      1：从数据库查询的用户信息   2.用户的密码（加密后）
        *                       3： 转换类型后的盐值     4.当前realm名字*/
        SimpleAuthenticationInfo saInfo = new SimpleAuthenticationInfo(
                user, user.getPassword(), byteSourceSalt, getName());
        return saInfo; /*返回值返回对象 底层交给认证器对象完成后续认证操作*/
    }
}
